package org.c41.media;

import java.io.ByteArrayInputStream;
import java.io.ByteArrayOutputStream;
import java.io.Flushable;
import java.io.IOException;

import javax.sound.sampled.AudioFileFormat;
import javax.sound.sampled.AudioFormat;
import javax.sound.sampled.AudioInputStream;
import javax.sound.sampled.AudioSystem;
import javax.sound.sampled.LineUnavailableException;
import javax.sound.sampled.TargetDataLine;

import org.c41.system.ICloseable;
import org.c41.system.ImpossibleException;

public class AudioRecorder implements ICloseable,Flushable{

	public static final int UNKNOWN = 0;
	public static final int CLOSED = 1;
	public static final int STOPPED = 2;
	public static final int RUNNING = 3;

	private static AudioRecorder one;

	public static AudioRecorder getSystemRecorder(){
		if(one == null){
			synchronized (AudioRecorder.class) {
				if(one == null){
					try {
						one = new AudioRecorder();
					} catch (LineUnavailableException e) {
					}
				}
			}
		}
		return one;
	}

	private TargetDataLine line;
	private int state;
	private AudioFormat format;

	private ByteArrayOutputStream out = new ByteArrayOutputStream();

	private AudioRecorder() throws LineUnavailableException{
		this.format = C41Media.getBasicTargetFormat();
		this.line = C41Media.createTargetDataLine(format);
		this.state = CLOSED;
	}

	public void open(){
		if(this.state!=CLOSED){
			m_call_illegal_state();
		}
		try {
			line.open(format, line.getBufferSize());
			this.state=STOPPED;
		} catch (LineUnavailableException e) {
			this.state = UNKNOWN;
		}
	}

	public void start(){
		if(this.state!=STOPPED){
			m_call_illegal_state();
		}
		Thread thread = new Thread(new Runnable() {
			@Override
			public void run() {
				m_run0();
			}
		});
		this.state=RUNNING;
		thread.start();
	}

	public void stop(){
		if(this.state!=RUNNING){
			m_call_illegal_state();
		}
		this.state = STOPPED;
	}

	public int getState(){
		return this.state;
	}

	public byte[] getBytes(){
		byte audioBytes[] = this.out.toByteArray();
		ByteArrayInputStream bais = new ByteArrayInputStream(audioBytes);
		AudioInputStream audioInputStream = new AudioInputStream(
				bais, 
				format,
				audioBytes.length / format.getFrameSize());
		ByteArrayOutputStream result=new ByteArrayOutputStream();
		try {
			AudioSystem.write(audioInputStream, AudioFileFormat.Type.WAVE, result);
			result.close();
		} catch (IOException e) {
		}
		return result.toByteArray();
	}

	@Override
	public void close(){
		if(state==CLOSED){
			return;
		}
		this.line.close();
		this.state=CLOSED;
	}

	@Override
	public boolean isClosed() {
		return this.state == CLOSED;
	}

	@Override
	public void flush() throws IOException {
		this.out.reset();
	}

	private void m_run0(){
		int frameSizeInBytes = format.getFrameSize();
		int bufferLengthInFrames = line.getBufferSize() / 8;
		int bufferLengthInBytes = bufferLengthInFrames * frameSizeInBytes;
		byte[] data = new byte[bufferLengthInBytes];
		int numBytesRead;
		line.start();
		while (this.state == RUNNING) {
			System.err.println("running");
			if ((numBytesRead = line.read(data, 0, bufferLengthInBytes)) == -1) {
				break;
			}
			out.write(data, 0, numBytesRead);
		}
		line.stop();
	}

	private void m_call_illegal_state(){
		switch(this.state){
		case RUNNING: throw new IllegalStateException("RUNNING");
		case CLOSED: throw new IllegalStateException("CLOSED");
		case STOPPED: throw new IllegalStateException("STOPPED");
		case UNKNOWN: throw new IllegalStateException("UNKNOWN");
		default:throw new ImpossibleException();
		}
	}

}
